
package com.easyseata.saga.interceptor;

import static com.easyseata.common.ConfigurationKeys.TCC_ACTION_INTERCEPTOR_ORDER;
import static  com.easyseata.common.Constants.BEAN_NAME_SPRING_FENCE_CONFIG;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.MDC;

import com.easyseata.common.Constants;
import com.easyseata.common.DefaultValues;
import com.easyseata.common.holder.ObjectHolder;
import com.easyseata.common.interceptor.InvocationWrapper;
import com.easyseata.common.interceptor.SeataInterceptorPosition;
import com.easyseata.common.interceptor.handler.AbstractProxyInvocationHandler;
import com.easyseata.common.util.ReflectionUtil;
import com.easyseata.config.core.ConfigurationFactory;
import com.easyseata.core.context.RootContext;
import com.easyseata.core.model.BranchType;
import com.easyseata.core.model.TwoPhaseBusinessActionParam;
import com.easyseata.integration.tx.api.fence.config.CommonFenceConfig;
import com.easyseata.saga.api.SagaAction;

public class SagaActionInterceptorHandler extends AbstractProxyInvocationHandler {

    private static final int ORDER_NUM = ConfigurationFactory.getInstance().getInt(TCC_ACTION_INTERCEPTOR_ORDER,
            DefaultValues.TCC_ACTION_INTERCEPTOR_ORDER);

    protected ActionInterceptorHandler actionInterceptorHandler = new ActionInterceptorHandler();

    private Set<String> methodsToProxy;
    protected Object targetBean;

    protected Map<Method, Annotation> parseAnnotationCache = new ConcurrentHashMap<>();

    public SagaActionInterceptorHandler(Object targetBean, Set<String> methodsToProxy) {
        this.targetBean = targetBean;
        this.methodsToProxy = methodsToProxy;
    }

    @Override
    protected Object doInvoke(InvocationWrapper invocation) throws Throwable {
        if (!RootContext.inGlobalTransaction() || RootContext.inSagaBranch()) {
            //not in transaction, or this interceptor is disabled
            return invocation.proceed();
        }
        Method method = invocation.getMethod();
        Annotation businessAction = parseAnnotation(method);

        //try method
        if (businessAction != null) {
            //save the xid
            String xid = RootContext.getXID();
            //save the previous branchType
            BranchType previousBranchType = RootContext.getBranchType();
            //if not saga, bind saga branchType
            if (getBranchType() != previousBranchType) {
                RootContext.bindBranchType(getBranchType());
            }
            try {
                TwoPhaseBusinessActionParam businessActionParam = createTwoPhaseBusinessActionParam(businessAction);
                //Handler the TCC Aspect, and return the business result
                return actionInterceptorHandler.proceed(method, invocation.getArguments(), xid, businessActionParam,
                        invocation::proceed);
            } finally {
                //if not TCC, unbind branchType
                if (getBranchType() != previousBranchType) {
                    RootContext.unbindBranchType();
                }
                //MDC remove branchId
                MDC.remove(RootContext.MDC_KEY_BRANCH_ID);
            }
        }

        //not TCC try method
        return invocation.proceed();
    }

    private Annotation parseAnnotation(Method methodKey) throws NoSuchMethodException {
        Annotation result = parseAnnotationCache.computeIfAbsent(methodKey, method -> {
            Annotation twoPhaseBusinessAction = method.getAnnotation(getAnnotationClass());
            if (twoPhaseBusinessAction == null && targetBean.getClass() != null) {
                Set<Class<?>> interfaceClasses = ReflectionUtil.getInterfaces(targetBean.getClass());
                if (interfaceClasses != null) {
                    for (Class<?> interClass : interfaceClasses) {
                        try {
                            Method m = interClass.getMethod(method.getName(), method.getParameterTypes());
                            twoPhaseBusinessAction = m.getAnnotation(getAnnotationClass());
                            if (twoPhaseBusinessAction != null) {
                                // init common fence clean task if enable useTccFence
                                initCommonFenceCleanTask(twoPhaseBusinessAction);
                                break;
                            }
                        } catch (NoSuchMethodException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
            return twoPhaseBusinessAction;
        });
        return result;
    }

    protected TwoPhaseBusinessActionParam createTwoPhaseBusinessActionParam(Annotation annotation) {
    	SagaAction businessAction = (SagaAction) annotation;
        TwoPhaseBusinessActionParam businessActionParam = new TwoPhaseBusinessActionParam();
        businessActionParam.setActionName(businessAction.name());
        businessActionParam.setDelayReport(businessAction.isDelayReport());
        businessActionParam.setUseCommonFence(businessAction.useTCCFence());
        businessActionParam.setBranchType(getBranchType());
        Map<String, Object> businessActionContextMap = new HashMap<>(4);
        //the phase two method name
        businessActionContextMap.put(Constants.COMMIT_METHOD, businessAction.commitMethod());
        businessActionContextMap.put(Constants.ROLLBACK_METHOD, businessAction.rollbackMethod());
        businessActionContextMap.put(Constants.ACTION_NAME, businessAction.name());
        businessActionContextMap.put(Constants.USE_COMMON_FENCE, businessAction.useTCCFence());
        businessActionParam.setBusinessActionContext(businessActionContextMap);
        return businessActionParam;
    }

    protected boolean parserCommonFenceConfig(Annotation annotation) {
        if (annotation == null) {
            return false;
        }
        SagaAction businessAction = (SagaAction) annotation;
        return businessAction.useTCCFence();
    }

    protected BranchType getBranchType() {
        return BranchType.SAGA;
    }

    protected Class<? extends Annotation> getAnnotationClass() {
        return SagaAction.class;
    }

    /**
     * init common fence clean task if enable useTccFence
     *
     * @param twoPhaseBusinessAction the twoPhaseBusinessAction
     */
    private void initCommonFenceCleanTask(Annotation twoPhaseBusinessAction) {
        CommonFenceConfig commonFenceConfig = (CommonFenceConfig) ObjectHolder.INSTANCE.getObject(BEAN_NAME_SPRING_FENCE_CONFIG);
        if (commonFenceConfig == null || commonFenceConfig.getInitialized().get()) {
            return;
        }
        if (twoPhaseBusinessAction != null && parserCommonFenceConfig(twoPhaseBusinessAction)) {
            if (commonFenceConfig.getInitialized().compareAndSet(false, true)) {
                // init common fence clean task if enable useTccFence
                commonFenceConfig.init();
            }
        }
    }

    @Override
    public Set<String> getMethodsToProxy() {
        return methodsToProxy;
    }

    @Override
    public int getOrder() {
        return ORDER_NUM;
    }

    @Override
    public SeataInterceptorPosition getPosition() {
        return SeataInterceptorPosition.Any;
    }

    @Override
    public int order() {
        return 1;
    }

    @Override
    public String type() {
        return "SagaAction";
    }
}
